package index;

import heap.RID;

import java.io.*;

import storage.data.Page;
import storage.data.PageID;

import Common.*;
import exceptions.*;
import Common.Types;
import Transaction.*;

public class BT implements GlobalConst {

	public final static int KeyCompare(KeyClass key1, KeyClass key2)
			throws KeyNotMatchException {
		if ((key1 instanceof IntegerKey) && (key2 instanceof IntegerKey)) {
			return (((IntegerKey) key1).getKey()).intValue()
					- (((IntegerKey) key2).getKey()).intValue();

		} else if ((key1 instanceof StringKey) && (key2 instanceof StringKey)) {
			return ((StringKey) key1).getKey().compareTo(
					((StringKey) key2).getKey());
		} else if ((key1 instanceof FloatKey) && (key2 instanceof FloatKey)) {
			return ((FloatKey) key1).getKey().compareTo(((FloatKey) key2).getKey()); 
			
		} else {
			//System.out.println("Type:"+((FloatKey)key1).getKey().floatValue());
			throw new KeyNotMatchException(null, "BT:Key type not match.");
		}
	}

	protected final static int getKeyLength(KeyClass key)
			throws KeyNotMatchException, IOException {
		if (key instanceof StringKey) {

			int length = ((StringKey) key).getKey().length();
			return length+4;
		} else if (key instanceof IntegerKey){
			return 4;
		} else if(key instanceof FloatKey) {
			return 4;
		}
		else
			throw new KeyNotMatchException(null, "key types do not match");
	}

	protected final static int getDataLength(short pageType)
			throws NodeNotMatchException {
		if (pageType == NodeType.LEAF)
			return 8;
		else if (pageType == NodeType.INDEX) {
			return 4;
		} else {
			throw new NodeNotMatchException("BT:K Node type not match.");

		}
	}

	/**
	 * It gets the length of the (key,data) pair in leaf or index page.
	 * 
	 * @param key
	 * @param pageType
	 * @return
	 * @throws KeyNotMatchException
	 * @throws NodeNotMatchException
	 * @throws IOException
	 */

	protected final static int getKeyDataLength(KeyClass key, short pageType,
			Transaction tx, String table) throws KeyNotMatchException,
			NodeNotMatchException, IOException {
		return getKeyLength(key) + getDataLength(pageType);
	}

	public final static KeyDataEntry getEntryFromBytes(byte[] from, int offset,
			int length, int keyType, short nodeType, Transaction tx,
			String table) throws Exception {
		KeyClass key;
		DataClass data;

		int nodelen;
		if (nodeType == NodeType.INDEX) {
			nodelen = 4;
			data = new IndexData(Convert.getIntValue(offset + length - 4, from,
					tx, table));
		} else if (nodeType == NodeType.LEAF) {
			nodelen = 8;
			RID rid = new RID();
			rid.slotNo = Convert.getIntValue(offset + length - 8, from, tx,
					table);
			rid.pageNo = new PageID();
			rid.pageNo.pageId = Convert.getIntValue(offset + length - 4, from,
					tx, table);
			data = new LeafData(rid);
		} else {
			throw new NodeNotMatchException("BT: node types not match");
		}

		if (keyType == Types.INT||keyType==Types.BOOLEAN) {
			key = new IntegerKey(new Integer(Convert.getIntValue(offset, from,
					tx, table)));
		} else if (keyType == Types.CHAR||keyType==Types.DATETIME||keyType==Types.TIMESTAMP||keyType==Types.VARCHAR||keyType==Types.DECIMAL) {
			key = new StringKey(Convert.getStrValue(offset, from, tx, table));
		} else if(keyType == Types.FLOAT) {
			key = new FloatKey(Convert.getFloValue(offset, from, tx, table));
			
		}else {
			throw new KeyNotMatchException(null, "key types do not match");
		}

		return new KeyDataEntry(key, data);
	}

	public final static byte[] getBytesFromEntry(KeyDataEntry entry)
			throws Exception {
		byte[] data;
		int n, m;
		n = getKeyLength(entry.key);
		m = n;
		if (entry.data instanceof IndexData) {
			n += 4;
		} else if (entry.data instanceof LeafData) {
			n += 8;
		}

		data = new byte[n];

		if (entry.key instanceof IntegerKey) {
			ByteArray.writeInt(data, 0, ((IntegerKey) entry.key).getKey()
					.intValue());
		} else if (entry.key instanceof StringKey) {
			ByteArray.writeString(data, 0, ((StringKey) entry.key).getKey());
		} else if (entry.key instanceof FloatKey) {
			ByteArray.writeFloat(data, 0, ((FloatKey) entry.key).getKey());
		} else {
			throw new KeyNotMatchException(null, "key types do not match");
		}
		if (entry.data instanceof IndexData) {
			Convert.SsetIntValue(((IndexData) entry.data).getData().pageId, m,
					data);
		} else if (entry.data instanceof LeafData) {
			Convert.SsetIntValue(((LeafData) entry.data).getData().slotNo, m,
					data);
			Convert.SsetIntValue(
					((LeafData) entry.data).getData().pageNo.pageId, m + 4,
					data);
		} else {
			throw new NodeNotMatchException("node types do not match");
		}
		return data;
	}

	public static void printBTree(BTHeaderPage header, Transaction tx,
			String table) throws Exception {
		if (header.getRootId(tx, table).pageId == INVALID) {
			System.out.println("The Tree is Empty!!!");
			return;
		}
		// System.out.println("header"+" "+header.curPage.pageId);
		System.out.println("");
		System.out.println("");
		System.out.println("");
		System.out
				.println("---------------The B+ Tree Structure---------------");

		System.out.println(1 + "     " + header.getRootId(tx, table).pageId);

		_printTree(header.getRootId(tx, table), "     ", 1, header.getKeyType(
				tx, table), tx, table);

		System.out.println("--------------- End ---------------");
		System.out.println("");
		System.out.println("");
	}

	private static void _printTree(PageID currentPageId, String prefix, int i,
			int keyType, Transaction tx, String table) throws Exception {

		BTPage sortedPage = new BTPage(currentPageId, keyType);
		prefix = prefix + "       ";
		i++;
		if (sortedPage.getType(tx, table) == NodeType.INDEX) {
			IndexPage indexPage = new IndexPage((Page) sortedPage, keyType, tx,
					table);

			System.out.println(i + prefix
					+ indexPage.getPrevPage(tx, table).pageId);
			_printTree(indexPage.getPrevPage(tx, table), prefix, i, keyType,
					tx, table);

			RID rid = new RID();
			for (KeyDataEntry entry = indexPage.getFirst(rid, tx, table); entry != null; entry = indexPage
					.getNext(rid, tx, table)) {
				System.out.println(i + prefix + (IndexData) entry.data);
				_printTree(((IndexData) entry.data).getData(), prefix, i,
						keyType, tx, table);
			}
		}
		GoggleDB.bufferMgr.unpinPage(currentPageId, true/* dirty */);
	}

	public static void printAllLeafPages(BTHeaderPage header, Transaction tx,
			String table) throws Exception {
		if (header.getRootId(tx, table).pageId == INVALID) {
			System.out.println("The Tree is Empty!!!");
			return;
		}

		System.out.println("");
		System.out.println("");
		System.out.println("");
		System.out
				.println("---------------The B+ Tree Leaf Pages---------------");

		_printAllLeafPages(header.getRootId(tx, table), header.getKeyType(tx,
				table), tx, table);

		System.out.println("");
		System.out.println("");
		System.out
				.println("------------- All Leaf Pages Have Been Printed --------");
		System.out.println("");
		System.out.println("");
	}

	private static void _printAllLeafPages(PageID currentPageId, int keyType,
			Transaction tx, String table) throws Exception {

		BTPage sortedPage = new BTPage(currentPageId, keyType);

		if (sortedPage.getType(tx, table) == NodeType.INDEX) {
			IndexPage indexPage = new IndexPage((Page) sortedPage, keyType, tx,
					table);

			_printAllLeafPages(indexPage.getPrevPage(tx, table), keyType, tx,
					table);

			RID rid = new RID();
			for (KeyDataEntry entry = indexPage.getFirst(rid, tx, table); entry != null; entry = indexPage
					.getNext(rid, tx, table)) {
				_printAllLeafPages(((IndexData) entry.data).getData(), keyType,
						tx, table);
			}
		}

		if (sortedPage.getType(tx, table) == NodeType.LEAF) {
			printPage(currentPageId, keyType, tx, table);
		}

		GoggleDB.bufferMgr.unpinPage(currentPageId, true/* dirty */);
	}

	public static void printPage(PageID pageno, int keyType, Transaction tx,
			String table) throws Exception {
		BTPage sortedPage = new BTPage(pageno, keyType);
		int i;
		i = 0;
		if (sortedPage.getType(tx, table) == NodeType.INDEX) {
			IndexPage indexPage = new IndexPage((Page) sortedPage, keyType, tx,
					table);
			System.out.println("");
			System.out.println("**************To Print an Index Page ********");
			System.out.println("Current Page ID: "
					+ indexPage.getCurPage(tx, table).pageId);
			System.out.println("Left Link      : "
					+ indexPage.getLeftLink(tx, table).pageId);

			RID rid = new RID();

			for (KeyDataEntry entry = indexPage.getFirst(rid, tx, table); entry != null; entry = indexPage
					.getNext(rid, tx, table)) {
				if (keyType == Types.INT)
					System.out.println(i + " (key, pageId):   ("
							+ (IntegerKey) entry.key + ",  "
							+ (IndexData) entry.data + " )");
				if (keyType == Types.CHAR)
					System.out.println(i + " (key, pageId):   ("
							+ (StringKey) entry.key + ",  "
							+ (IndexData) entry.data + " )");

				i++;
			}

			System.out.println("************** END ********");
			System.out.println("");
		} else if (sortedPage.getType(tx, table) == NodeType.LEAF) {
			LeafPage leafPage = new LeafPage((Page) sortedPage, keyType, tx,
					table);
			System.out.println("");
			System.out.println("**************To Print an Leaf Page ********");
			System.out.println("Current Page ID: "
					+ leafPage.getCurPage(tx, table).pageId);
			System.out.println("Left Link      : "
					+ leafPage.getPrevPage(tx, table).pageId);
			System.out.println("Right Link     : "
					+ leafPage.getNextPage(tx, table).pageId);

			RID rid = new RID();

			for (KeyDataEntry entry = leafPage.getFirst(rid, tx, table); entry != null; entry = leafPage
					.getNext(rid, tx, table)) {
				if (keyType == Types.INT)
					System.out.println(i + " (key, [pageNo, slotNo]):   ("
							+ (IntegerKey) entry.key + ",  "
							+ (LeafData) entry.data + " )");
				if (keyType == Types.CHAR)
					System.out.println(i + " (key, [pageNo, slotNo]):   ("
							+ (StringKey) entry.key + ",  "
							+ (LeafData) entry.data);
				if(keyType == Types.FLOAT)
					System.out.println(i + " (key, [pageNo, slotNo]):   ("
							+ (FloatKey) entry.key + ",  "
							+ (LeafData) entry.data);

				i++;
			}

			System.out.println("************** END ********");
			System.out.println("");
		} else {
			System.out
					.println("Sorry!!! This page is neither Index nor Leaf page.");
		}

		GoggleDB.bufferMgr.unpinPage(pageno, true/* dirty */);
	}
}
